import axios from 'axios';
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';

const TWENTY_API_URL: string =
  process.env.TWENTY_API_URL !== '' && process.env.TWENTY_API_URL !== undefined
    ? `${process.env.TWENTY_API_URL}/rest`
    : 'https://api.twenty.com/rest';
const TWENTY_API_KEY: string = process.env.TWENTY_API_KEY ?? '';
const MAILCHIMP_API_URL: string =
  process.env.MAILCHIMP_SERVER_PREFIX !== '' &&
  process.env.MAILCHIMP_SERVER_PREFIX !== undefined
    ? `https://${process.env.MAILCHIMP_SERVER_PREFIX}.api.mailchimp.com/3.0/`
    : '';
const MAILCHIMP_API_KEY: string = process.env.MAILCHIMP_API_KEY ?? '';
const MAILCHIMP_AUDIENCE_ID: string = process.env.MAILCHIMP_AUDIENCE_ID ?? '';
const IS_EMAIL_CONSTRAINT: boolean = process.env.IS_EMAIL_CONSTRAINT === 'true';
const IS_COMPANY_CONSTRAINT: boolean =
  process.env.COMPANY_CONSTRAINT === 'true';
const IS_PHONE_CONSTRAINT: boolean = process.env.IS_PHONE_CONSTRAINT === 'true';
const IS_ADDRESS_CONSTRAINT: boolean =
  process.env.IS_ADDRESS_CONSTRAINT === 'true';
const UPDATE_PERSON: boolean = process.env.UPDATE_PERSON === 'true';

type mailchimpAddress = {
  street1: string;
  street2: string;
  city: string;
  state: string;
  zipCode: string;
  country: string;
};

type mailchimpRecord = {
  id?: string;
  email_channel?: {
    email: string;
    marketing_consent?: {
      status: string;
    };
  };
  sms_channel?: {
    sms_phone: string;
    marketing_consent?: {
      status: string;
    };
  };
  mergeFields: {
    FNAME: string;
    LNAME: string;
    ADDRESS: string | mailchimpAddress;
    COMPANY: string;
    PHONE: string;
  };
};

type twentyAddress = {
  addressStreet1: string;
  addressStreet2: string;
  addressCity: string;
  addressState: string;
  addressPostCode: string;
  addressCountry: string;
};

type twentyCompany = {
  name: string;
  address: twentyAddress;
};

type twentyPerson = {
  name: {
    firstName: string;
    lastName: string;
  };
  emails: {
    primaryEmail: string;
  };
  phones: {
    primaryPhoneNumber: string;
    primaryPhoneCallingCode: string;
  };
  companyId: string | null;
};

const fetchCompanyData = async (companyId: string): Promise<twentyCompany> => {
  const options = {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${TWENTY_API_KEY}`,
    },
    url: `${TWENTY_API_URL}/company/${companyId}`,
  };
  try {
    const response = await axios.request(options);
    return response.status === 200
      ? ({
          name: response.data.name as string,
          address: response.data.address as twentyAddress,
        } as twentyCompany)
      : ({} as twentyCompany);
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw error;
    }
    throw error;
  }
};

const checkAddress = (address: twentyAddress): mailchimpAddress => {
  if (
    address.addressStreet1 !== '' &&
    address.addressCity !== '' &&
    address.addressPostCode !== '' &&
    address.addressState !== ''
  ) {
    return {
      street1: address.addressStreet1,
      street2: address.addressStreet2,
      city: address.addressCity,
      state: address.addressState,
      zipCode: address.addressPostCode,
      country: address.addressCountry,
    } as mailchimpAddress;
  }
  throw new Error('Invalid address');
};

const checkAudiencePermissions = async (
  audienceId: string,
): Promise<string[] | undefined> => {
  const options = {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${MAILCHIMP_API_KEY}`,
    },
    url: `${MAILCHIMP_API_URL}/audiences/${audienceId}`,
  };
  try {
    const temp = await axios.request(options);
    return temp.data.enabled_channels;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw error;
    }
  }
};

const prepareData = (
  firstName: string,
  lastName: string,
  email: string,
  phoneNumber: string,
  phoneCallingCode: string,
  companyName: string,
  address: mailchimpAddress | string,
): mailchimpRecord => {
  let data = {
    mergeFields: {},
  } as mailchimpRecord;
  data.mergeFields.FNAME = firstName;
  data.mergeFields.LNAME = lastName;
  if (IS_EMAIL_CONSTRAINT) {
    data.email_channel = {
      email: email,
      marketing_consent: {
        status: 'unknown',
      },
    };
  }
  if (IS_ADDRESS_CONSTRAINT) {
    data.mergeFields.ADDRESS = address;
  }
  if (IS_PHONE_CONSTRAINT) {
    const mergedPhoneNumber: string = phoneCallingCode.startsWith('+')
      ? phoneCallingCode.concat(phoneNumber)
      : '+'.concat(phoneCallingCode, phoneNumber);
    data['sms_channel'] = {
      sms_phone: mergedPhoneNumber,
      marketing_consent: {
        status: 'unknown',
      },
    };
    data['mergeFields']['PHONE'] = mergedPhoneNumber;
  }
  if (IS_COMPANY_CONSTRAINT) {
    data['mergeFields']['COMPANY'] = companyName;
  }
  return data;
};

const addTwentyPersonToMailchimp = async (
  convertedRecord: mailchimpRecord,
): Promise<boolean | undefined> => {
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${MAILCHIMP_API_KEY}`,
    },
    url: `${MAILCHIMP_API_URL}/audiences/${MAILCHIMP_AUDIENCE_ID}/contacts`,
    data: convertedRecord,
  };
  try {
    const response = await axios.request(options);
    return response.status === 200;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw error;
    }
  }
};

const checkIfTwentyPersonExistsInMailchimp = async (
  email?: string,
  phoneNumber?: string,
  cursor?: string,
) => {
  const options = {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${MAILCHIMP_API_KEY}`,
    },
    url: cursor
      ? `${MAILCHIMP_API_URL}/audiences/${MAILCHIMP_AUDIENCE_ID}/contacts?count%3D1000%26cursor%3D${cursor}`
      : `${MAILCHIMP_API_URL}/audiences/${MAILCHIMP_AUDIENCE_ID}/contacts?count%3D1000`,
  };
  try {
    const response = await axios.request(options);
    const doesPersonExist: mailchimpRecord | undefined =
      (response.data.contacts.find(
        (contact: any) => contact.email_channel.email === email,
      ) as mailchimpRecord) ||
      (response.data.contacts.find(
        (contact: any) => contact.sms_channel.sms_phone === phoneNumber,
      ) as mailchimpRecord);
    if (doesPersonExist !== undefined) {
      return doesPersonExist;
    }
    if (response.data.next_cursor === undefined) {
      return undefined;
    } else {
      await checkIfTwentyPersonExistsInMailchimp(
        email,
        phoneNumber,
        response.data.next_cursor,
      );
    }
    return undefined;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw error;
    }
  }
};

const compareTwoRecords = (
  object1: mailchimpRecord,
  object2: mailchimpRecord,
) => {
  return (
    object1.email_channel?.email === object2.email_channel?.email &&
    object1.sms_channel?.sms_phone === object2.sms_channel?.sms_phone &&
    object1.mergeFields.FNAME === object2.mergeFields.FNAME &&
    object1.mergeFields.LNAME === object2.mergeFields.LNAME &&
    object1.mergeFields.ADDRESS === object2.mergeFields.ADDRESS &&
    object1.mergeFields.COMPANY === object2.mergeFields.COMPANY
  );
};

const updateTwentyPersonInMailchimp = async (
  mailchimpRecordId: string,
  twentyConvertedRecord: mailchimpRecord,
): Promise<boolean | undefined> => {
  const options = {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${MAILCHIMP_API_KEY}`,
      'Content-Type': 'application/json',
    },
    url: `${MAILCHIMP_API_URL}/audiences/${MAILCHIMP_AUDIENCE_ID}/contacts/${mailchimpRecordId}`,
    data: twentyConvertedRecord,
  };
  try {
    const response = await axios.request(options);
    return response.status === 200;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      throw error;
    }
  }
};

export const main = async (params: {
  properties: Record<string, any>;
  recordId: string;
  userId: string;
}): Promise<object | undefined> => {
  if (!IS_EMAIL_CONSTRAINT && !IS_PHONE_CONSTRAINT) {
    console.warn(
      'Function exited as there are no constraints to email nor phone number',
    );
    return {};
  }
  if (
    MAILCHIMP_API_URL === '' ||
    MAILCHIMP_API_KEY === '' ||
    MAILCHIMP_AUDIENCE_ID === ''
  ) {
    console.warn('Missing Mailchimp required parameters');
    return {};
  }
  if (IS_COMPANY_CONSTRAINT && TWENTY_API_KEY === '') {
    console.warn('Missing Twenty related parameters');
    return {};
  }

  try {
    const { properties } = params;
    const twentyRecord: twentyPerson = properties.after as twentyPerson;
    if (
      twentyRecord.name.firstName === '' ||
      twentyRecord.name.lastName === ''
    ) {
      throw new Error('First or last name is empty');
    }

    const audiencePermissions: string[] | undefined =
      await checkAudiencePermissions(MAILCHIMP_AUDIENCE_ID);

    if (
      IS_EMAIL_CONSTRAINT &&
      audiencePermissions?.includes('Email') &&
      twentyRecord.emails.primaryEmail === ''
    ) {
      throw new Error('Email is empty');
    }

    if (
      IS_PHONE_CONSTRAINT &&
      audiencePermissions?.includes('SMS') &&
      (twentyRecord.phones.primaryPhoneNumber === '' ||
        twentyRecord.phones.primaryPhoneCallingCode === '')
    ) {
      throw new Error('Phone number is empty');
    }

    let companyName: string = '';
    let address: mailchimpAddress | string = '';
    if (IS_COMPANY_CONSTRAINT) {
      if (twentyRecord.companyId === null) {
        throw new Error('Missing relation to company record');
      }
      const company: twentyCompany = await fetchCompanyData(
        twentyRecord.companyId,
      );
      companyName = company.name ?? ''; // either "" or name
      address = IS_ADDRESS_CONSTRAINT ? checkAddress(company.address) : '';
    }

    const twentyPersonToMailchimpRecord: mailchimpRecord = prepareData(
      twentyRecord.name.firstName,
      twentyRecord.name.lastName,
      twentyRecord.emails.primaryEmail,
      twentyRecord.phones.primaryPhoneNumber,
      twentyRecord.phones.primaryPhoneCallingCode,
      companyName,
      address,
    );
    console.log(twentyPersonToMailchimpRecord);

    const isTwentyPersonInMailchimp: mailchimpRecord | undefined =
      await checkIfTwentyPersonExistsInMailchimp(
        twentyRecord.emails.primaryEmail,
        twentyPersonToMailchimpRecord.sms_channel?.sms_phone,
      );
    if (isTwentyPersonInMailchimp !== undefined) {
      console.log(
        `Person ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} found in Mailchimp`,
      );
      if (UPDATE_PERSON) {
        if (
          !compareTwoRecords(
            isTwentyPersonInMailchimp,
            twentyPersonToMailchimpRecord,
          ) &&
          isTwentyPersonInMailchimp.id
        ) {
          const isTwentyPersonUpdatedInMailchimp: boolean | undefined =
            await updateTwentyPersonInMailchimp(
              isTwentyPersonInMailchimp.id,
              twentyPersonToMailchimpRecord,
            );
          if (!isTwentyPersonUpdatedInMailchimp) {
            throw new Error(
              `Person ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} update in Mailchimp failed`,
            );
          } else {
            console.log(
              `Person ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} update in Mailchimp succeeded`,
            );
          }
        } else {
          console.log(
            `Person ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} wasn't updated in Mailchimp because they're the same`,
          );
        }
      } else {
        console.log(
          `Person ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} wasn't updated in Mailchimp because UPDATE_PERSON is set to false`,
        );
      }
    } else {
      console.log(
        `${twentyRecord.name.firstName} ${twentyRecord.name.lastName} doesn't exist in Mailchimp, adding`,
      );
      const isTwentyPersonAddedToMailchimp: boolean | undefined =
        await addTwentyPersonToMailchimp(twentyPersonToMailchimpRecord);
      if (!isTwentyPersonAddedToMailchimp) {
        throw new Error(
          `Adding ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} person to Mailchimp failed`,
        );
      } else {
        console.log(
          `Person ${twentyRecord.name.firstName} ${twentyRecord.name.lastName} has been successfully added`,
        );
      }
    }
    return {};
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.error(error.response);
      return {};
    }
    console.error(error);
    return {};
  }
};

export const config: ServerlessFunctionConfig = {
  universalIdentifier: '83319670-775b-4862-b133-5c353e594151',
  name: 'mailchimp-synchronizer',
  triggers: [
    {
      universalIdentifier: 'e627ff6f-0a0c-48b2-bdbb-31967489ec96',
      type: 'databaseEvent',
      eventName: 'person.created',
    },
    {
      universalIdentifier: '657ece26-4478-4408-a257-4e9e16cce279',
      type: 'databaseEvent',
      eventName: 'person.updated',
    },
  ],
};
